from django.template import defaulttags
from django.templatetags import i18n
from django.template import Node, TemplateSyntaxError
from django.core.urlresolvers import reverse
from django.utils.http import urlencode
from django.utils import translation
from django.utils.encoding import smart_str

from itertools import chain, islice, izip as zip
import re

import _translation

#XX Handle errors appropriately, for instance raise some informative error
#XX when a menu is not in the menu dict instead of a simple KeyError

_locmenu_dict = {}
_menu_dict = {}

def item(name='', verbose_name='', view='', kwargs=None, query=None):
    if isinstance(query, dict):
        query = query.items()
    return name, verbose_name, view, kwargs or {}, query or ()


def _register_menu(name, items, is_loc):
    #XX compare performance with more simple versions
    #pre_result stores items that don't depend on kwargs
    if is_loc:
        menu_dict = _locmenu_dict
        set_language_into_url = _translation.set_language_into_url
        get_language = translation.get_language
        def loc_dict(d, l):
            d.update(lang_code=l)
            return d
    else:
        menu_dict = _menu_dict
        set_language_into_url = lambda x, y, z: x
        get_language = lambda: None
        loc_dict = lambda x, y: x
    
    pre_result = [None]*sum(len(hc) for hc, x in zip(*[iter(items)]*2))
    
    def menu(kwargs):
        lang_code = get_language()
        result = pre_result[:]
        i_result = enumerate(result)
        for hard_coded, param in zip(*[iter(items)]*2):
            if param:
                name_p, vname_p, view_p, kw_p, query_p = param
                name_h = name_p and ('%(' + name_p + ')s') % kwargs
                vname_h = vname_p and ('%(' + vname_p + ')s') % kwargs
                view_h = view_p and ('%(' + view_p + ')s') % kwargs
                kw_h = dict((k, ('%(' + v + ')s') % kwargs) for k, v in kw_p.iteritems())
                query_h = [(k, ('%(' + v + ')s') % kwargs) for k, v in query_p]
                for item, (i, pre_item) in zip(hard_coded, i_result):
                    if pre_item: continue
                    name, vname, view, kw, query = item
                    name = name or name_h
                    vname = vname or vname_h
                    view = view or view_h
                    kw = dict(kw_h, **kw)
                    query = list(chain(query_h, query))
                    r = view and reverse(view, kwargs=loc_dict(kw, lang_code))
                    q = urlencode(query)
                    result[i] = (name, vname, r + (q and '?' + q))
            else:
                for item, (i, pre_item) in zip(hard_coded, i_result):
                    name, vname, url = pre_item
                    result[i] = (name, vname, set_language_into_url(url, lang_code, 1))
        return result
    
    def menu0(kwargs):
        lang_code = get_language()
        result = pre_result[:]
        i_result = range(len(result))
        for hard_coded, param in zip(*[iter(items)]*2):
            if param:
                name_p, vname_p, view_p, kw_p, query_p = param
                name_h = name_p and ('%(' + name_p + ')s') % kwargs
                vname_h = vname_p and ('%(' + vname_p + ')s') % kwargs
                view_h = view_p and ('%(' + view_p + ')s') % kwargs
                kw_h = dict((k, ('%(' + v + ')s') % kwargs) for k, v in kw_p.iteritems())
                query_h = [(k, ('%(' + v + ')s') % kwargs) for k, v in query_p]
                for item, i in zip(hard_coded, i_result):
                    name, vname, view, kw, query = item
                    perm = name and vname and view #determines whether to store in pre_result
                    name = name or name_h
                    vname = vname or vname_h
                    view = view or view_h
                    if kw_h:
                        kw = dict(kw_h, **kw)
                        perm = False
                    else:
                        kw = kw.copy() #since loc_dict will modify it in-place
                    if query_h:
                        query = list(chain(query_h, query))
                        perm = False
                    r = view and reverse(view, kwargs=loc_dict(kw, lang_code))
                    q = urlencode(query)
                    item = (name, vname, r + (q and '?' + q))
                    if perm:
                        pre_result[i] = item
                    result[i] = item
            else:
                for item, i in zip(hard_coded, i_result):
                    name, vname, view, kw, query = item
                    r = view and reverse(view, kwargs=loc_dict(kw, lang_code))
                    q = urlencode(query)
                    pre_result[i] = result[i] = (name, vname, r + (q and '?' + q))     
                      
        menu_dict[name] = menu
        return result  
       
    menu_dict[name] = menu0
    
    
def register_menu(name, *items):
    return _register_menu(name, items, False)

def register_locmenu(name, *items):
    return _register_menu(name, items, True)
    
#-----------------------------------------------Template Tags-------------------------------------------
          
class MenuNode(Node):
    menu_dict = _menu_dict
    def __init__(self, menu_name, kwargs, var_name):
        self.menu_name = menu_name
        self.kwargs = kwargs
        self.var_name = var_name
        
    def render(self, context):
        kwargs = dict((smart_str(k,'ascii'), v.resolve(context))
                       for k, v in self.kwargs.iteritems())
        context[self.var_name] = self.menu_dict[self.menu_name](kwargs)
        return ''
    
class LocMenuNode(MenuNode):
    menu_dict = _locmenu_dict

@defaulttags.register.tag
def menu(parser, token):
    return MenuNode(*_menu(parser, token))

@i18n.register.tag   
def locmenu(parser, token):
    return LocMenuNode(*_menu(parser, token))

kwarg_re  = re.compile(r"(\w+)=(.+)")
def _menu(parser, token):
    bits = token.split_contents()
    tag_name = bits[0]
    if len(bits) < 4:
        raise TemplateSyntaxError("'%s' takes at least two arguments"
                                  " (path to a view and 'as var')" % tag_name)
    menu_name = bits[1]
    kwargs = {}
    if bits[-2] != 'as':
        raise TemplateSyntaxError("Malformed 'as var' to '%s' tag" % tag_name)
    var_name = bits[-1]
    
    for bit in islice(bits, 2, len(bits) - 2):
        match = kwarg_re.match(bit)
        if not match:
            raise TemplateSyntaxError("Malformed arguments to '%s' tag" % tag_name)
        name, value = match.groups()
        kwargs[name] = parser.compile_filter(value)  
        
    return menu_name, kwargs, var_name
        
        